package org.webscale.util;

import java.io.Serializable;
import java.net.InetAddress;
import java.util.Random;

/**
 * This class represents a Universally Unique Identifier, often referred
 * to as a UUID.  This class implements the following capabilities :
 * <li>The ability to generate a UUID.</li>
 * <li>The ability to retrieve a UUID as a string.</li>
 * <li>The ability to retrieve a UUID as a sequence of bytes.</li>
 * <li>The ability to instantiate a UUID in a number of ways.</li>
 * <li>The ability to compare UUIDs with each other.</li>
 * <p>
 * The format for representing a UUID string consists of eight hexadecimal
 * digits followed by a dash, followed by three groups of four hexadecimal
 * digits separated by dashes, followed by a dash and twelve hexadecimal
 * digits: <code>0123467-89AB-CDEF-0123-456789ABCDEF</code>.
 * <p>
 * Once created, a <code>UUID</code> can be shared in a read-only way
 * between threads.
 *
 * @author Steven Murray , Niraj Juneja
 */
public class UUID implements Cloneable, Serializable {
	/**
	 * Comment for <code>serialVersionUID</code>
	 */
	private static final long serialVersionUID = -2549643239217087449L;
	private static short clockseq = -1;
	private static Random rgen = new Random();

	/**
	 * Generates a new UUID.
	 * @return A new <code>UUID</code> object.
	 */
	public static UUID newInstance() {
		return new UUID();
	}

	/**
	 * This method will create a new <code>UUID</code> object based on the
	 * provided hex String.
	 * @param hexString A hexString UUID, like that provided from the
	 * <code>getHexString</code>.
	 * @return A new <code>UUID</code> with the same UUID as was provided
	 * in the hexidecimal String, or null if the specified String is less
	 * than 32 characters long.
	 * @see #getHexString
	 */
	public static UUID newInstance(String hexString) {
		if (hexString.length() != 32) {
			return null;
		}
		byte[] hexBytes = new byte[16];
		int index = 0; //to avoid div by 0
		for (int j = 0; j < hexString.length(); j = j + 2) {
			String twoChar = hexString.substring(j, j + 2);
			int v = Integer.parseInt(twoChar, 16);
			if (j > 0) {
				index = j / 2;
			}
			hexBytes[index] = new Integer(v).byteValue();
		}
		return new UUID(hexBytes);
	}

	/**
	 * Generates a null UUID (all zeroes).
	 * @return A <code>UUID</code> object with a value of all zeroes.
	 */
	public static UUID nullUUID() {
		byte values[] = new byte[16];
		return new UUID(values);
	}

	/**
	 * This method will generate a new UUID and return it's hexidecimal
	 * String.
	 * @return The hex string corresponding to a newly generated UUID.
	 */
	public static String newString() {
		UUID u = new UUID();
		return u.toHexString();
	}

	private int _data1 = 0;
	private short _data2 = 0;
	private short _data3 = 0;
	private byte _data4[] = null;

	/**
	 * Constructs a new <code>UUID</code> using the UUID creation algorithm.
	 */
	public UUID() {
		_data4 = new byte[8];

		// work on data 1
		long msecs = System.currentTimeMillis();

		// add on time between 1/1/70 and 10/15/1592, roughly
		msecs += (long) (1970 - 1593) * 365 * 24 * 60 * 60 * 1000;
		msecs += (long) 77 * 24 * 60 * 60 * 1000;

		// ticks is 100 nonosecond ticks
		long ticks = msecs * 1000 * 1000 / 100;

		// get the clock sequence
		short clockval = 0;
		synchronized (rgen) {
			if (clockseq == -1) {
				clockseq = (short) (rgen.nextInt() & 0xffff);
			}
			if (clockseq < 0)
				clockseq *= -1;
			clockval = clockseq++;
		}

		// data 1 is low 4 bytes of timestamp
		_data1 = (int) (ticks & 0xffffffff);

		// data 2 is bytes 5 and 6
		_data2 = (short) ((ticks >>> 32) & 0x0000ffff);

		// data 3 is bytes 7 and 8
		_data3 = (short) ((ticks >>> 48) & 0x0000ffff);

		// clock sequence for data4[0-1]
		_data4[0] = (byte) ((clockval & 0xff00) >>> 8);
		_data4[1] = (byte) (clockval & 0x00ff);

		// now get the next 2 values using random
		synchronized (rgen) {
			int intVal;
			for (int i = 2; i < 4; i++) {
				intVal = rgen.nextInt();
				_data4[i] = (byte) (0xff & intVal);
			}
		}

		// get ip address
		InetAddress ia;

		try {
			ia = InetAddress.getLocalHost();

			// loop over ip address numbers
			byte[] abytes = ia.getAddress();
			for (int i = 0; i < 4 && i < abytes.length; i++) {
				_data4[i + 4] = abytes[i];
			}
		} catch (java.net.UnknownHostException e) {
			// use random if we fail
			synchronized (rgen) {
				int intVal;
				for (int i = 0; i < 4; i++) {
					intVal = rgen.nextInt();
					_data4[i + 4] = (byte) (0xff & intVal);
				}
			}
		}
	}

	/**
	 * Constructs a <code>UUID</code> from the 4 base values.
	 * @param d1 data value 1 from the uuid structure
	 * @param d2 data value 2 from the uuid structure
	 * @param d3 data value 3 from the uuid structure
	 * @param d4 data value 4 from the uuid structure
	*/
	public UUID(int d1, short d2, short d3, byte[] d4) {
		_data4 = new byte[8];
		_data1 = d1;
		_data2 = d2;
		_data3 = d3;
		for (int i = 0; i < 8; i++) {
			_data4[i] = d4[i];
		}
	}

	/**
	 * Constructs a <code>UUID</code> from a UUID string.
	 * The string should be either in aabbccdd format, or aa-bb-cc-dd format
	 * (with or without hypens).  This corresponds to either
	 * <code>getUUIDString ()</code>, <code>toString ()</code>, or
	 * <code>getHexString ()</code> formats.
	 * @param s a string representation of a UUID to construct a
	 * <code>UUID</code> from.
	 */
	public UUID(String s) {
		_data4 = new byte[8];

		// parse the string
		String s1, s2, s3, s4, s5;
		int hyphen1 = 0;
		int hyphen2 = 0;
		int hyphen3 = 0;
		int hyphen4 = 0;

		// find the hyphens
		hyphen1 = s.indexOf('-');
		hyphen4 = s.lastIndexOf('-');
		hyphen2 = s.indexOf('-', hyphen1 + 1);
		hyphen3 = s.indexOf('-', hyphen2 + 1);

		// parse the values
		if ((hyphen1 > 0) && (hyphen2 > 0) && (hyphen3 > 0) && (hyphen4 > 0)) {
			s1 = s.substring(0, hyphen1);
			s2 = s.substring(hyphen1 + 1, hyphen2);
			s3 = s.substring(hyphen2 + 1, hyphen3);
			s4 = s.substring(hyphen3 + 1, hyphen4);
			s5 = s.substring(hyphen4 + 1);
		} else if (s.length() == 32) {
			s1 = s.substring(0, 8);
			s2 = s.substring(8, 12);
			s3 = s.substring(12, 16);
			s4 = s.substring(16, 20);
			s5 = s.substring(20, 32);
		} else {
			return;
		}

		// convert to data the first 3n
		_data1 = (int) Long.parseLong(s1, 16);
		_data2 = (short) Integer.parseInt(s2, 16);
		_data3 = (short) Integer.parseInt(s3, 16);

		// convert 4 and 5 to data
		String hexString;
		for (int i = 0; i < 2; i++) {
			hexString = s4.substring(2 * i, (2 * i) + 2);
			_data4[i] = (byte) Integer.parseInt(hexString, 16);
		}
		for (int i = 2; i < 8; i++) {
			hexString = s5.substring((2 * i) - 4, (2 * i) - 2);
			_data4[i] = (byte) Integer.parseInt(hexString, 16);
		}

		// all set
	}

	/**
	 * Constructs a <code>UUID</code> from the raw data.
	 * @param d the byte stream of raw data
	 */
	public UUID(byte d[]) {
		_data4 = new byte[8];

		// get data 1
		_data1 = (int) ((d[0] << 24) & 0xff000000);
		_data1 |= (d[1] << 16) & 0x00ff0000;
		_data1 |= (d[2] << 8) & 0x0000ff00;
		_data1 |= d[3] & 0x000000ff;

		// get data 2
		_data2 = (short) ((d[4] << 8) & 0xff00);
		_data2 |= d[5] & 0x00ff;

		// get data 3
		_data3 = (short) ((d[6] << 8) & 0xff00);
		_data3 |= d[7] & 0x00ff;

		// get data 4
		for (int i = 0; i < 8; i++) {
			_data4[i] = d[i + 8];
		}
	}

	/**
	 * Creates an identical copy of this object.
	 * @return a new <code>UUID</code> object identical to the current one.
	 */
	public Object myClone() {
		return new UUID(_data1, _data2, _data3, _data4);
	}

	/**
	 * Compares the specified <code>UUID</code> for equality with the
	 * current <code>UUID</code>.
	 * @param obj The <code>UUID</code> to compare against.  If this is
	 * not an object of type <code>UUID</code>, this method will return
	 * false.
	 * @return True if equal, false if not
	 */
	public boolean equals(Object obj) {
		if (this == obj)
			return true;

		if (obj == null)
			return false;

		if (obj instanceof UUID) {
			UUID u = (UUID) obj;

			return (compare(u, this) == 0);
		}

		return (false);
	}

	/**
	 * This method compares two <code>UUID</code> objects.
	 * @return Zero if equal, otherwise a non-zero value.
	 * @param uuid1 The first <code>UUID</code>.
	 * @param uuid2 The second <code>UUID</code>.
	 */
	public static int compare(UUID uuid1, UUID uuid2) {
		if (uuid1._data1 != uuid2._data1)
			return (uuid1._data1 - uuid2._data1);
		if (uuid1._data2 != uuid2._data2)
			return (uuid1._data2 - uuid2._data2);
		if (uuid1._data3 != uuid2._data3)
			return (uuid1._data3 - uuid2._data3);
		for (int i = 0; i < 8; i++) {
			if (uuid1._data4[i] != uuid2._data4[i])
				return (uuid1._data4[i] - uuid2._data4[i]);
		}

		// they are equal
		return 0;
	}

	/**
	 * Determines the hash key for this UUID.  Useful if you want to store
	 * <code>UUID</code>s in a hash table.
	 * @return hash value
	 */
	public int hashCode() {
		int hash = _data1 + 3 * _data2 + 5 * _data3;
		if (_data4 != null) {
			for (int i = 0; i < _data4.length; i++) {
				hash += _data4[i];
			}
		}
		return hash;
	}

	/**
	 * This method provides the specified value as a String, with padding of
	 * zeroes to the specified number of places.
	 * @param val The value to be obtained as a String.
	 * @param padding The number of places the String should be padded to.
	 * @return The zero padded textual representation of the specified value.
	 */
	private String padIt(int val, int padding) {
		String s = Integer.toHexString(val).toUpperCase();
		String pad = "";

		// all ok
		if (s.length() == padding)
			return s;

		// if too big
		if (s.length() > padding) {
			return s.substring(s.length() - padding);
		}

		for (int i = 0; i < padding - s.length(); i++) {
			pad = pad + "0";
		}

		return (pad + s);
	}

	/**
	 * Returns the string representation for a <code>UUID</code>.
	 * @return the string representation for the <code>UUID</code>.
	 */
	public String toString() {
		String ret =
			padIt(_data1, 8) + "-" + padIt(_data2, 4) + "-" + padIt(_data3, 4) + "-";
		for (int i = 0; i < 2; i++) {
			ret += padIt(_data4[i], 2);
		}
		ret += "-";
		for (int i = 2; i < 8; i++) {
			ret += padIt(_data4[i], 2);
		}

		return ret;
	}

	/**
	 * This method provides a textual representation of this <code>UUID</code>
	 * as a hexidecimal String.  (No hyphens will be embedded.)  Although
	 * this format can not be parsed by any constructor, it can be passed to
	 * <code>fromHexString</code> to obtain a <code>UUID</code>.
	 * @return Returns a string of 16 hex characters (32 ascii characters).
	 * @see #fromHexString(java.lang.String)
	 */
	public String toHexString() {
		StringBuffer retval = new StringBuffer();
		byte[] values = getBytes();

		// build each character and append it
		for (int i = 0; i < values.length; i++) {
			retval.append(padIt(values[i], 2));
		}

		// return
		return retval.toString();
	}

	/**
	 * Returns the raw data representation for a <code>UUID</code>.
	 * @return The raw representation in bytes
	 */
	public byte[] getBytes() {
		byte[] retBytes = new byte[16];

		// get data 1
		retBytes[0] = (byte) ((_data1 >> 24) & 0xff);
		retBytes[1] = (byte) ((_data1 >> 16) & 0xff);
		retBytes[2] = (byte) ((_data1 >> 8) & 0xff);
		retBytes[3] = (byte) (_data1 & 0xff);

		// get data 2
		retBytes[4] = (byte) ((_data2 >> 8) & 0xff);
		retBytes[5] = (byte) (_data2 & 0xff);

		// get data 3
		retBytes[6] = (byte) ((_data3 >> 8) & 0xff);
		retBytes[7] = (byte) (_data3 & 0xff);

		// data data 4
		for (int i = 8; i < 16; i++) {
			retBytes[i] = _data4[i - 8];
		}

		// return
		return retBytes;
	}
}